package cn.felord.security.autoconfigure;

import org.springframework.lang.NonNull;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * @author felord.cn
 * @since 2021/8/6 18:40
 */
public class SecureUser implements UserDetails, CredentialsContainer {
    private static final long serialVersionUID = 560L;
    private String password;
    private final String userId;
    private final String customId;
    private final String clientId;
    private final String username;
    private final Set<GrantedAuthority> authorities;
    private final boolean accountNonExpired;
    private final boolean accountNonLocked;
    private final boolean credentialsNonExpired;
    private final boolean enabled;

    public SecureUser(@NonNull String userId, @NonNull String customId, @NonNull String clientId,@NonNull String username, String password, Collection<GrantedAuthority> authorities) {
        this(userId, customId,clientId, username, password, true, true, true, true, authorities);
    }

    public SecureUser(@NonNull String userId,@NonNull String customId,@NonNull String clientId, @NonNull String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<GrantedAuthority> authorities) {
        Assert.isTrue(!"".equals(username) && password != null, "Cannot pass null or empty values to constructor");
        this.userId = userId;
        this.customId = customId;
        this.clientId = clientId;
        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.accountNonExpired = accountNonExpired;
        this.credentialsNonExpired = credentialsNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
    }

    public String getUserId() {
        return this.userId;
    }

    public String getCustomId() {
        return customId;
    }

    public String getClientId() {
        return clientId;
    }

    public Collection<GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    public String getPassword() {
        return this.password;
    }

    public String getUsername() {
        return this.username;
    }

    public boolean isEnabled() {
        return this.enabled;
    }

    public boolean isAccountNonExpired() {
        return this.accountNonExpired;
    }

    public boolean isAccountNonLocked() {
        return this.accountNonLocked;
    }

    public boolean isCredentialsNonExpired() {
        return this.credentialsNonExpired;
    }

    public void eraseCredentials() {
        this.password = null;
    }

    private static SortedSet<GrantedAuthority> sortAuthorities(Collection<GrantedAuthority> authorities) {
        Assert.notNull(authorities, "Cannot pass a null GrantedAuthority collection");
        SortedSet<GrantedAuthority> sortedAuthorities = new TreeSet<>(new AuthorityComparator());
        sortedAuthorities.add(new SimpleGrantedAuthority("AUTHENTICATED"));
        String anonymous = "ANONYMOUS";
        for (GrantedAuthority grantedAuthority : authorities) {
            Assert.notNull(grantedAuthority, "GrantedAuthority list cannot contain any null elements");
            if (!anonymous.equals(grantedAuthority.getAuthority())) {
                sortedAuthorities.add(grantedAuthority);
            }
        }
        return sortedAuthorities;
    }

    public boolean equals(Object obj) {
        return obj instanceof SecureUser && this.username.equals(((SecureUser) obj).username);
    }

    public int hashCode() {
        return this.username.hashCode();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.getClass().getName()).append(" [");
        sb.append("UserId=").append(this.userId).append(", ");
        sb.append("CustomId=").append(this.customId).append(", ");
        sb.append("ClientId=").append(this.clientId).append(", ");
        sb.append("Username=").append(this.username).append(", ");
        sb.append("Password=[PROTECTED], ");
        sb.append("Enabled=").append(this.enabled).append(", ");
        sb.append("AccountNonExpired=").append(this.accountNonExpired).append(", ");
        sb.append("credentialsNonExpired=").append(this.credentialsNonExpired).append(", ");
        sb.append("AccountNonLocked=").append(this.accountNonLocked).append(", ");
        sb.append("Granted Authorities=").append(this.authorities).append("]");
        return sb.toString();
    }

    private static class AuthorityComparator implements Comparator<GrantedAuthority>, Serializable {
        private static final long serialVersionUID = 560L;

        private AuthorityComparator() {
        }

        public int compare(GrantedAuthority g1, GrantedAuthority g2) {
            if (g2.getAuthority() == null) {
                return -1;
            } else {
                return g1.getAuthority() == null ? 1 : g1.getAuthority().compareTo(g2.getAuthority());
            }
        }
    }
}